/*
 * SART.java
 *
 * Revision:
 * Author                                       	Date           
 * ---------------------------------------------	----------   
 * Francisco Gomes de Oliveira Neto					2014-06-18
 */
package br.edu.ufcg.splab.techniques.regression;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

import br.edu.ufcg.splab.core.InterfaceEdge;
import br.edu.ufcg.splab.core.InterfaceGraph;
import br.edu.ufcg.splab.generator.TestCase;
import br.edu.ufcg.splab.generator.TestCaseGenerator;
import br.edu.ufcg.splab.generator.TestSuite;
import br.edu.ufcg.splab.techniques.reduction.heuristics.Harrold;
import br.edu.ufcg.splab.techniques.reduction.requirement.RequirementTracer;
import br.edu.ufcg.splab.techniques.reduction.requirement.SingleTransitionsTracerGenerator;
import br.edu.ufcg.splab.util.ReadTGF;

public class SART {

	public static void main(String[] args) {
		try{
			String folder = "test-data\\regression\\";
			ReadTGF read = new ReadTGF();
			InterfaceGraph baselineLTS = read.getGraph(folder+"qual_baseline.tgf");
			InterfaceGraph deltaLTS = read.getGraph(folder+"qual_delta.tgf");

			TestCaseGenerator gen = new TestCaseGenerator();
			TestSuite baseline = gen.generate(baselineLTS);
			TestSuite delta = gen.generate(deltaLTS);
			
			SART w = new SART();
			TestSuite ts = w.reduce(baseline, delta);
			System.out.println(ts.toString());
		
		}catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
	
	private static void print(TestSuite baseline) {
		System.out.println(baseline.toString().replaceAll("\\(DEFAULT\\)", ""));
		
	}

	private TestSuite reduced;
	//These are the right references for classification done in method analyzematrix
	private TestSuite obsolete;
	private TestSuite targeted;
	private TestSuite reusable;
	
	public TestSuite getObsolete() {
		return obsolete;
	}

	public TestSuite getTargeted() {
		return targeted;
	}

	public TestSuite getReusable() {
		return reusable;
	}

	/**
	 * Performs Test Case selection by comparing similarities among two test suites.
	 * This method does not uses minimisation techniques.
	 * 
	 * @param baselineTS The baseline test suite
	 * @param deltaTS The delta test suite
	 * @return Returns a subset from the delta test suite covering the modifications.
	 */
	public TestSuite select(TestSuite baselineTS, TestSuite deltaTS){
		//The similarity matrix
		double[][] matrix = generateMatrix(baselineTS, deltaTS);
		return selectFromMatrix(matrix, baselineTS, deltaTS);
		//generate matrix
		//classify test cases
		//get weights
		//select test cases.
	}
	
	/**
	 * This is the default selection performed by SART. It includes the similarity analysis
	 * and a test suite minimisation technique based on the H heuristic.
	 * 
	 * @param baselineTS The baseline test suite.
	 * @param deltaTS The delta test suite.
	 * @return A subset of test cases containing targeted and reusable test cases.
	 */
	public TestSuite reduce(TestSuite baselineTS, TestSuite deltaTS){
		TestSuite subset  = select(baselineTS, deltaTS);

		Harrold h = new Harrold();

		SingleTransitionsTracerGenerator t = new SingleTransitionsTracerGenerator();
		RequirementTracer reqTracer = t.getTracer(subset);
		List<RequirementTracer> list = new ArrayList<RequirementTracer>();
		list.add(reqTracer);
		//reduced = h.reduceTestSuite(subset, reqTracer);
		reduced = h.reduceTestSuite(subset, list);
		//print(baselineTS);
	
		int clutter = subset.size()- reduced.size();

		TestSuite finalSubset = new TestSuite();
		finalSubset.addAll(reduced);
		double[][] traditionalMatrix = generateMatrix(reusable, reduced);
//		System.out.println(printMatrix(traditionalMatrix));
		for (int i = 0; i < clutter; i++) {
			int row = i%reduced.size();
			double highestSim = traditionalMatrix[row][0];
			int chosenTC = 0;
			for(int column = 1; column < traditionalMatrix[row].length; column++){
				if(traditionalMatrix[row][column] > highestSim){
					highestSim = traditionalMatrix[row][column];
					chosenTC = column;
				}else if(traditionalMatrix[row][column] == highestSim && Math.random() < 0.5){
					//random choice
					highestSim = traditionalMatrix[row][column];
					chosenTC = column;					
				}
			}
			finalSubset.add(reusable.get(chosenTC));
			removeColumn(traditionalMatrix, chosenTC);
		}
//		print(finalSubset);
		return finalSubset;
	}
	
	private void removeColumn(double[][] traditionalMatrix, int chosenTC) {
		for (int i = 0; i < traditionalMatrix.length; i++) {
			traditionalMatrix[i][chosenTC] = -1.0;
		}
	}

	/**
	 * Creates the similarity matrix based on the test cases for different 
	 * versions of the specification model.
	 * 
	 * @param baselineTS The baseline test suite
	 * @param deltaTS The baseline test suite
	 * @return The similarity matrix containing the similarity value 
	 * between test cases from the baseline (columns) and delta (rows) versions
	 */
	public double[][] generateMatrix(TestSuite baselineTS, TestSuite deltaTS){
		double[][] matrix = new double[deltaTS.size()][baselineTS.size()];
		for (int i = 0; i < deltaTS.size(); i++) {
			TestCase deltaTC = deltaTS.get(i);
			for (int j = 0; j < baselineTS.size(); j++) {
				TestCase baselineTC = baselineTS.get(j);
				//Gets the similarity between a pair of test cases.
				double similarity = getSimilarity(deltaTC, baselineTC);
				matrix[i][j] = similarity;
			}
		}
		
		//printMatrix(matrix);
		return matrix;
	}

	/**
	 * Prints a similarity matrix. This is just for debugging or printing purposes.
	 * @param matrix The similarity matrix that needs to be printed. Similarity 
	 * values equal to zero will be printed as '-.--
	 * 
	 * @return A string representing the similarity matrix.
	 */
	public String printMatrix(double[][] matrix) {
		DecimalFormat df = new DecimalFormat("#0.00");
		String eol = System.getProperty("line.separator");
		StringBuffer matrixSTR = new StringBuffer();
		
		for (int i = 0; i < matrix.length; i++) {
			for (int j = 0; j < matrix[i].length; j++) {
				if(matrix[i][j] == 0.0){
					matrixSTR.append("-.--");
				}else{
					matrixSTR.append(df.format(matrix[i][j]));	
				}
				
				matrixSTR.append(" ");
			}
			matrixSTR.append(eol);
		}
		return matrixSTR.toString();
		
	}

	/**
	 * Classifies and then select test cases from the matrix. Test cases can be
	 *  obsolete, reusable and targeted. The selected subset must contain delta 
	 *  test cases similar to obsolete test cases, and all targeted test cases.
	 * @param matrix The similarity matrix containing similarities among all pair of test cases.
	 * @param baseline The baseline test suite.
	 * @param delta The delta test suite.
	 * @return Aside from the selected subset, this method also defines the class attributes,
	 * by determining the set of obsolete, reusable and targeted test cases.
	 */
	private TestSuite selectFromMatrix(double[][] matrix, TestSuite baseline, TestSuite delta){
		obsolete = new TestSuite();
		targeted = new TestSuite();
		reusable = new TestSuite();
		
		TestSuite subset = new TestSuite();
		
		for (int column = 0; column < matrix[0].length; column++) {
			boolean isObsolete = true;
			double highestSimilarity = matrix[0][column];
			int mostSimilarIndex = 0;
			for (int row = 0; row < matrix.length; row++) {
				if(matrix[row][column] > highestSimilarity){
					highestSimilarity = matrix[row][column];
					mostSimilarIndex = row;
				}
				if(matrix[row][column] == 1.0){
					//non-obsolete
					isObsolete = false;
					row = matrix.length; //skip to next column
				}				
			}
			if(isObsolete){
				obsolete.add(baseline.get(column));
				//Adds only if the test case was not previously added.
				addToSubset(subset, delta.get(mostSimilarIndex));
			}
		}
		
		for (int i = 0; i < matrix.length; i++) {
			boolean isRetestable = true;
			for (int j = 0; j < matrix[i].length; j++) {
				if(matrix[i][j] == 1.0){
					isRetestable = false;
					j = matrix[i].length;
				}				
			}

			if(isRetestable){
				targeted.add(delta.get(i));
				//Adds only if the test case was not previously added.
				addToSubset(subset, delta.get(i));
				
			}else if(!isRetestable){
				reusable.add(delta.get(i));
			}
		}
		return subset;
	}

	/**
	 * This is an auxiliary method to avoid insertion of repeated test cases in a test suite.
	 * For refactoring purposes only.
	 * 
	 * @param subset The subset to try insertion.
	 * @param testCase The test case to be added.
	 * @return A boolean value indicating if the operation was successful.
	 */
	private boolean addToSubset(TestSuite subset, TestCase testCase) {
		if(!subset.contains(testCase)){
			subset.add(testCase);
			return true;
		}else{
			return false;
		}
	}

	private void printList(List<TestCase> suite) {
		for(TestCase tc : suite){
			System.out.println(tc);
		}
		System.out.println();
	}

	/**
	 * Assigns a similarity value between two test cases.
	 * @param tc1 Test case 1.
	 * @param tc2 Test case 2.
	 * @return The similarity between both test cases regarding the states and transitions being covered.
	 */
	private double getSimilarity(TestCase tc1, TestCase tc2) {
		List<InterfaceEdge> transitions1 = tc1.getTestCase();
		List<InterfaceEdge> transitions2 = tc2.getTestCase();
		
		//Number of Identical Transitions
		double nit = 0;
		for(InterfaceEdge transition1 : transitions1){			
			for (int i = 0; i < transitions2.size(); i++) {
				InterfaceEdge transition2 = transitions2.get(i);
//				if(transition1.getLabel().equals(transition2.getLabel()) &&
//						transition1.getFrom().getLabel().equals(transition2.getFrom().getLabel()) &&
//						transition1.getTo().getLabel().equals(transition2.getTo().getLabel())){
				if(transition1.getLabel().equals(transition2.getLabel())){
					nit = nit + 1.0; 
					i = transitions2.size(); //skip to the next transition.
				}
			}			
		}
		double avgSize = (double)(transitions1.size()+transitions2.size())/2.0;
		double similarity = nit/avgSize;
		return similarity;
	}

	
	public TestSuite getReduced() {
		return reduced;
	}
}
